<?php
/**
 * Here all framework core functions are defined.
 *
 * The most of the here defined functions are for better string handling. The following functions are contained:
 *
 * - {@see \UF\strlen()}
 * - {@see \UF\substr()}
 * - {@see \UF\strpos()}
 * - {@see \UF\str_lastpos()}
 * - {@see \UF\str_positions()}
 * - {@see \UF\str_positions_multi()}
 * - {@see \UF\str_startswith()}
 * - {@see \UF\str_endswith()}
 * - {@see \UF\str_contains()}
 * - {@see \UF\mask_xml()}
 * - {@see \UF\mask_arg()}
 * - {@see \UF\unmask_xml()}
 * - {@see \UF\str_max()}
 * - {@see \UF\str_ireplace()}
 * - {@see \UF\strip_tags()}
 * - {@see \UF\str_extract_after()}
 * - {@see \UF\str_extract_after_last()}
 * - {@see \UF\str_extract_before()}
 * - {@see \UF\str_extract_before_last()}
 * - {@see \UF\str_extract_between()}
 * - {@see \UF\str_extract_between_minmax()}
 *
 * @category   UniKap-Framework
 * @package    Core
 * @subpackage Functions
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF
{

    # <editor-fold defaultstate="collapsed" desc="strlen + substr">

    /**
     * Returns the length of a string with a defined encoding.
     * Encapsules PHPs {@link http://php.net/mb_strlen mb_strlen} function.
     *
     * @param      string $str The string
     * @param      string $charset Encoding of the string (defaults to 'UTF-8')
     * @return     int
     * @uses       Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since      v0.1
     */
    function strlen( $str, $charset='UTF-8' )
    {
        if ( ! \MB_ENABLED ) throw new \UF\MissingExtensionException( 'Multibyte', '\\UF\\strlen fails.' );
        return \mb_strlen( $str, $charset );
    }

    /**
     * Extracts a sub string from $str with a defined encoding.
     *
     * @param      string $str The string to work with.
     * @param      int    $start Start index (0-n) where the extraction begins.
     * @param      int    $length Length og the substring to extract or all, if the default value NULL is used.
     *             It also can use a negative value.
     * @param      string $charset Encoding of the string (defaults to 'UTF-8')
     * @return     string
     * @uses       Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since      v0.1
     */
    function substr( $str, $start, $length=null, $charset='UTF-8' )
    {
        if ( \is_null($str) ) { return ''; }
        if ( \is_null($length) ) { $length = \UF\strlen ( $str, $charset ) - $start; }
        if ( ! \MB_ENABLED ) throw new \UF\MissingExtensionException( 'Multibyte', '\\UF\\substr fails.' );
        return \mb_substr( $str, $start, $length, $charset );
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="strpos + str_lastpos + str_positions + str_positions_multi">

    /**
     * Returns the first position (index 0-n) of $needle inside $str, or (bool)FALSE if $needle is not contained.
     *
     * @param  string $str The string.
     * @param  string $needle The sub string to locate in $str
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @param  int    $offset Start position of search, or NULL
     * @return int|(bool)FALSE
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function strpos( $str, $needle, $caseless=false, $charset='UTF-8', $offset=null )
    {
        if ( \is_null($str) || \is_null($needle) || \strlen($needle) < 1 || \strlen($str) < 1 ) { return false; }
        if ( ! \MB_ENABLED ) throw new \UF\MissingExtensionException( 'Multibyte', '\\UF\\strpos fails.' );
        if ( $caseless ) { $result =  \mb_stripos( $str, $needle, $offset, $charset ); }
        else { $result = \mb_strpos( $str, $needle, $offset, $charset ); }
        if ( $result < 0 ) { return false; }
        return $result;
    }

    /**
     * Returns the last position (index 0-n) of $needle inside $str, or (bool)FALSE if $needle is not contained.
     *
     * @param  string $str The string.
     * @param  string $needle The sub string to locate in $str
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @return int|(bool)FALSE
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_lastpos( $str, $needle, $caseless=false, $charset='UTF-8' )
    {
        if ( \is_null($str) || \is_null($needle) || \strlen($needle) < 1 || \strlen($str) < 1 ) { return false; }
        if ( ! \MB_ENABLED ) throw new \UF\MissingExtensionException( 'Multibyte', '\\UF\\str_lastpos fails.' );
        if ( $caseless ) { $idx =  \mb_strripos( $str, $needle, null, $charset ); }
        else { $idx = \mb_strrpos( $str, $needle, null, $charset ); }
        if ( $idx < 0 ) { return false; }
        return $idx;
    }

    /**
     * Returns all Positions of $needle in $str.
     *
     * @param  string $str The string.
     * @param  string $needle The sub string to locate in $str
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @return array|FALSE Return a numeric indicated array with all positions of $needle in $str or (bool)FALSE
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_positions( $str, $needle, $caseless=false, $charset='UTF-8' )
    {
        $result = array();
        if ( false === ( $idx = \UF\strpos( $str, $needle, $caseless, $charset ) ) ) { return false; }
        $result[] = $idx;
        $needleLen = \UF\strlen( $needle, $charset );
        while ( \FALSE !== ($next = \UF\strpos( $str, $needle, $caseless, $charset, $idx + $needleLen ) ) )
        {
            $idx = $next;
            $result[] = $idx;
        }
        return $result;
    }

    /**
     * Returns all positions of the defined $needles in $str.
     *
     * @param  string $str The string.
     * @param  array  $needles All sub string to locate in $str.
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @return array
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_positions_multi(
        $str, array $needles, $caseless=false, $charset='UTF-8' )
    {
        $res = array();
        foreach ($needles as $needle)
        {
            if ( \FALSE === ( $tmp = \UF\str_positions($str, $needle, $caseless, $charset) ) ) { continue; }
            $res = \array_merge($res, $tmp);
        }
        if ( \count($res) > 0 ) { \sort( $res, \SORT_NUMERIC ); }
        return $res;
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="str_startswith + str_endswith + str_contains">

    /**
     * Returns, if $str starts with $needle.
     *
     * @param  string $str      The string to check.
     * @param  string $needle   The string, where $str must starts with
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @return bool
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_startswith( $str, $needle, $caseless=false, $charset='UTF-8' )
    {
        return ( 0 === \UF\strpos( $str, $needle, $caseless, $charset ) );
    }

    /**
     * Returns, if $str ends with $needle.
     *
     * @param  string $str      The string to check.
     * @param  string $needle   The string, where $str must ends with
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset  Encoding of the string (defaults to 'UTF-8')
     * @return bool
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_endswith( $str, $needle, $caseless=false, $charset='UTF-8' )
    {
        if ( !\is_string( $str ) || !\is_string( $needle ) ) { return false; }
        $nl = \UF\strlen( $needle, $charset );
        $sl = \UF\strlen( $str, $charset );
        if ( ( $nl < 1 ) || ( $sl < $nl ) ) { return false; }
        return ( ($sl - $nl) == \UF\str_lastpos($str, $needle, $caseless, $charset) );
    }

    /**
     * Returns, if $str contains $needle.
     *
     * @param  string $str      The string to check.
     * @param  string $needle   he string, where $str must contains
     * @param  bool   $caseless Ignore the case? (defaults to FALSE)
     * @param  string $charset  Encoding of the string (defaults to 'UTF-8')
     * @return bool
     * @uses   Multibyte extension The function requires that PHP have the Multibyte extension mb_string enabled.
     * @since  v0.1
     */
    function str_contains( $str, $needle, $caseless=false, $charset='UTF-8')
    {
        return ( FALSE !== \UF\strpos($str, $needle, $caseless, $charset) );
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="mask_xml + mask_arg + unmask_xml">

    /**
     * Escapes a string for use as a XML ELement value. The characters &amp;&lt;&gt; will be converted to entities.
     *
     * @param  string $str The string
     * @return string
     * @since  v0.1
     */
    function mask_xml( $str )
    {
        return \str_replace(
            array( '&',     '<',    '>' ),
            array( '&amp;', '&lt;', '&gt;' ),
            $str );
    }

    /**
     * Escapes a string for use as XML element attribute value. The chars &amp;&lt;&gt;" will be converted to entities.
     *
     * @param  string $str The string
     * @return string
     * @since  v0.1
     */
    function mask_arg( $str )
    {
        return \str_replace(
            array( '&',     '<',    '>',    '"' ),
            array( '&amp;', '&lt;', '&gt;', '&quot;' ),
            $str );
    }

    /**
     * Unescapes (HT|X)ML entities in a string.
     *
     * @param  string $str The string
     * @param  bool   $full Unescape all entities?
     * @return string
     * @since  v0.1
     */
    function unmask_xml( $str, $full=false )
    {
        $search = !$full
            ? array( '&auml;', '&Auml;', '&ouml;', '&Ouml;', '&uuml;', '&Uuml;',
                      '&lt;', '&gt;', '&quot;', '&amp;', '&deg;', '&szlig;' )
            : array( '~&(quot|#34|#034|#x22);~i', '~&(amp|#38|#038|#x26);~i', '~&(lt|#60|#060|#x3c);~i',
                     '~&(gt|#62|#062|#x3e);~i', '~&(nbsp|#160|#xa0);~i', '~&(iexcl|#161);~i', '~&(cent|#162);~i',
                     '~&(pound|#163);~i', '~&(copy|#169);~i', '~&(reg|#174);~i', '~&(deg|#176);~i',
                     '~&(#39|#039|#x27);~', '~&(euro|#8364);~i', '~&a(uml|UML);~', '~&o(uml|UML);~', '~&u(uml|UML);~',
                     '~&A(uml|UML);~', '~&O(uml|UML);~', '~&U(uml|UML);~', '~&szlig;~i', '~&#(\d{1,4});~e' );
        $replace = !$full
            ? array( 'ä','Ä','ö','Ö','ü','Ü','<','>','"','&','°','ß')
            : array( '"', '&', '<', '>', ' ', \chr(161), \chr(162), \chr(163), '©', '®', '°', \chr(39), '€',
                     'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß', 'chr($1)' );
        return !$full
            ? \str_replace($search, $replace, $str)
            : \preg_replace($search, $replace, $str);
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="str_max + str_ireplace + strip_tags">

    /**
     * Trim the string $str to a max. length and appends, if trimming is needet $appendix.
     *
     * <code>
     * // Example
     *
     * $str = 'My name is Max';
     * $newstr = \UF\str_max( $str, 11, 'Art' );
     * echo "'{$newstr}'";
     *
     * // Results in output: 'My name is Art'
     * </code>
     *
     * @param  string $str The string.
     * @param  int    $maxlength The resulting string max. length (incl. $appendix)
     * @param  string $appendix Optional appendix (Defaults to '…')
     * @param  string $charset Encoding of the string (defaults to 'UTF-8')
     * @return string
     * @since  v0.1
     */
    function str_max( $str, $maxlength, $appendix='…', $charset='UTF-8' )
    {
        if (\UF\strlen($str, $charset) <= $maxlength) { return $str; }
        return \UF\substr(
            $str, 0, $maxlength - \UF\strlen($appendix, $charset) ) .
            $appendix;
    }

    /**
     * str_replace with a caseless string handling.
     *
     * @param  string|array $search What will be replaced?
     * @param  string|array $replace The Replacement(s)
     * @param  string       $subject The String to work with
     * @param  bool         $useUnicode Use Unicode for internal regex? (defaults to TRUE)
     * @return string
     * @since  v0.1
     */
    function str_ireplace( $search, $replace, $subject, $useUnicode=true )
    {
        if ( !\is_array( $search ) ) { $search  = array( $search ); }
        for ( $i = 0, $j = \count( $search ); $i < $j; $i++ )
        {
            $search[$i] = \preg_quote( $search[$i] );
        }
        $search = '~(' . \join( '|', $search ) . ')~i' . ($useUnicode ? 'u' : '');
        return \preg_replace( $search, $replace, $subject );
    }

    /**
     * Removes all HTML/XML Markup from defined string.
     *
     * @param  string $str
     * @return string
     * @since  v0.1
     */
    function strip_tags( $str )
    {
        $search = array(
            '~<script[^>]*?>.*?</script>~siu',
            '~<style[^>]*?>.*?</style>~siu',
            '~<![\s\S]*?--[ \t\n\r]*>~u'
        );
        return \strip_tags( \preg_replace( $search, '', $str ) );
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="print_h">

    /**
     * Does a normal print_r but outputs inside &lt;pre&gt; Elements with a optional class-Attribute.
     *
     * @param  mixed $value The value to print out
     * @param  string $preClass Optional class attribute value of generated pre HTML element
     * @return string
     * @since  v0.1
     */
    function print_h( $value, $preClass=null )
    {
        echo '<pre',
            (!empty($preClass) ? " class=\"{$preClass}\">" : '>'),
            \UF\mask_xml( \print_r($value, true) ),
            '</pre>';
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="splitLines">

    /**
     * Explodes the defined string, at every line break (\r\n|\n|\r)
     *
     * @param string $string
     * @return array
     * @since  v0.1
     */
    function splitLines( $string )
    {
        return \preg_split( "~(\r\n|\n|\r)~", $string );
    }

    # </editor-fold>

    # <editor-fold defaultstate="collapsed" desc="Autoloader">

    /**
     * @access   private
     * @internal The Framework insternal class auto loader function
     * @ignore
     * @since    v0.1
     */
    function autoloader( $classname )
    {
        $classname = \ltrim( $classname, '\\' );
        if ( !\UF\str_startswith( $classname, 'UF\\' ) ) return false;
        $elements = explode( '\\', $classname );
        $file = \UF_BASE_FOLDER . '/' . join( '/', $elements ) . '.php';
        if ( !\file_exists( $file ) ) { return false; }
        require $file;
        return true;
    }

    \spl_autoload_register( '\UF\autoloader', true );

    # </editor-fold>

}

